package guowei.gulielasticsearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import guowei.common.to.es.EsModel;
import guowei.common.utils.R;
import guowei.gulielasticsearch.EsConstant.ProductConstant;
import guowei.gulielasticsearch.config.GuliEs;
import guowei.gulielasticsearch.feign.ProductFeignClient;
import guowei.gulielasticsearch.service.SearchEsService;
import guowei.gulielasticsearch.vo.*;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class SearchEsServiceImpl implements SearchEsService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Override
    public SearchResult getInfosByParams(SearchParam searchParam) {
        SearchRequest searchRequest = null;
        SearchResult searchResult = null;
        SearchResponse response = null;

//        1.准备检索请求
        searchRequest = requestBinding(searchParam);

//        2.执行检索数据
        try {
            response = client.search(searchRequest, GuliEs.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

//        3.分析响应数据
        searchResult = gainData(response,searchParam);
        return searchResult;
    }

    /**
     * 构建结果数据的方法
     * @param response
     * @return
     */
    private SearchResult gainData(SearchResponse response,SearchParam searchParam) {
        SearchResult searchResult = new SearchResult();
//  得到所有命中的记录信息
        SearchHits hits = response.getHits();

        /**
         * 1-4全部在hits里
         */
//  1.为返回结果赋上 所有商品信息
        List<EsModel> esModels = new ArrayList<>();
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                EsModel esModel = JSON.parseObject(sourceAsString, EsModel.class);

//    若有高亮skuTitle, 则覆盖esModel里的该属性
                if (!StringUtils.isEmpty(searchParam.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    Text[] fragments = skuTitle.getFragments();
                    String skuTitles = fragments[0].toString();
                    esModel.setSkuTitle(skuTitles);
                }
                esModels.add(esModel);
            }
        }
        searchResult.setProducts(esModels);
//  2.为返回结果赋上 当前页码信息
        searchResult.setPageNum(searchParam.getPageNum());

//  3.为返回结果赋上 总信息数
        long total = hits.getTotalHits().value;
        searchResult.setTotal(total);

//  4.为返回结果赋上 全部页数
        int pages = (int) (total % ProductConstant.PRODUCT_SIZE == 0 ? (total / ProductConstant.PRODUCT_SIZE) : (total / ProductConstant.PRODUCT_SIZE + 1));
        searchResult.setTotalPages(pages);

//  4.5 为返回结果赋上 导航页码
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= pages; i++) {
            pageNavs.add(i);
        }
        searchResult.setPageNavs(pageNavs);

        /**
         * 以下全部在aggs里
         */
//  5.为返回结果赋上 brand品牌信息
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        List<? extends Terms.Bucket> buckets1 = brand_agg.getBuckets();
        ArrayList<SearchResult.BrandVo> brandVos = new ArrayList<>();
        for (Terms.Bucket buckte : buckets1) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            Long brandId = (Long) buckte.getKey();
            brandVo.setBrandId(brandId);
            ParsedStringTerms brand_name_agg = buckte.getAggregations().get("brand_name_agg");
            String brandName = (String) brand_name_agg.getBuckets().get(0).getKey();
            brandVo.setBrandName(brandName);

            ParsedStringTerms brand_img_agg = buckte.getAggregations().get("brand_Img_agg");
            String brandImg = brand_img_agg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);

//  6.为返回结果赋上 catalog分类信息
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        List<? extends Terms.Bucket> buckets = catalog_agg.getBuckets();
        ArrayList<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
//            找到catalogId
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            Long catalogId = (Long) bucket.getKey();
            catalogVo.setCatalogId(catalogId);

//            找到catalogName
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            String cataLogName = (String) catalog_name_agg.getBuckets().get(0).getKey();
            catalogVo.setCatalogName(cataLogName);
//            将封装好的catalog分类信息，添加进集合中
            catalogVos.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVos);

//        todo 要为attrs属性赋上信息
//  7.为返回结果赋上 attr_id属性信息
        ArrayList<SearchResult.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        List<? extends Terms.Bucket> buckets2 = attr_id_agg.getBuckets();
        for (Terms.Bucket bucket : buckets2) {

//      7.0 为返回结果赋上 attr_id信息
            Long attrId = (Long) bucket.getKey();

//      7.1 为返回结果赋上 attr_name信息
            ParsedStringTerms aggs_attrName = bucket.getAggregations().get("aggs_attrName");
            String attrName = aggs_attrName.getBuckets().get(0).getKeyAsString();

//     7.2 为返回结果赋上 attr_value信息
            ParsedStringTerms aggs_attrValue = bucket.getAggregations().get("aggs_attrValue");
            List<String> attrvalues = aggs_attrValue.getBuckets().stream().map(bucket1 -> {
                String attrValues = bucket1.getKeyAsString();
                return attrValues;
            }).collect(Collectors.toList());

            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrvalues);
            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);

        List<SearchResult.NavVo> navVos = null;
//为返回结果设置上面包屑导航数据
        if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0) {
            navVos = searchParam.getAttrs().stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] split = attr.split("_");
//            为navVo设置value
                navVo.setNavValue(split[1]);
//            为navVo设置name 需要用到guliProduct服务
                Long attr_id = (long) Integer.parseInt(split[0]);

//          赋值请求中的attrId ，以便于页面显示数据
                searchResult.getAttrIds().add(attr_id);

                R info = productFeignClient.info(attr_id);

//          得到数据就赋值
                if (info.getCode() == 0) {
                    AttrRespVo attr1 = (AttrRespVo) info.getData("attr", new TypeReference<AttrRespVo>() {
                    });
                    navVo.setNavName(attr1.getAttrName());
                } else {
                    navVo.setNavName(split[0]);
                }
//            取消面包屑导航，需要替换url地址
                String replace = getReplace(searchParam, attr, "attrs");
                navVo.setLink("http://search.ganjiuwanle.com/list.html?" + replace);

                return navVo;
            }).collect(Collectors.toList());
        }

//  此时改变的是brandId   类似attrs属性，
        if (searchParam.getBrandId() != null && searchParam.getBrandId().size() > 0){
            R brand = productFeignClient.brandIdsinfo(searchParam.getBrandId());
                List<BrandRespVo> brands = (List<BrandRespVo>) brand.getData("brands", new TypeReference<List<BrandRespVo>>() {});
                List<SearchResult.NavVo> voList = brands.stream().map(b -> {
                    SearchResult.NavVo navVo = new SearchResult.NavVo();
                    navVo.setNavValue(b.getName());
                    navVo.setNavName("品牌");
                    String replace = getReplace(searchParam, b.getBrandId() + "", "brandId");
                    navVo.setLink("http://search.ganjiuwanle.com/list.html?"+replace);
                    return navVo;
                }).collect(Collectors.toList());
            if (navVos != null){
                navVos.addAll(voList);
            }else{
                navVos = voList;
            }
        }
        searchResult.setNavVos(navVos);

//  此时改变分类信息catalog
        if (searchParam.getCatalog3Id() != null){
            List<SearchResult.NavVo> navVos1 = new ArrayList<>();
            R catainfo = productFeignClient.catainfo(searchParam.getCatalog3Id());
            CatRespVo category = (CatRespVo) catainfo.getData("category", new TypeReference<CatRespVo>() {});
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavValue(category.getName());
            navVo.setNavName("分类");
            String replace = getReplace(searchParam,category.getCatId()+"", "catalog3Id");
            navVo.setLink("http://search.ganjiuwanle.com/list.html?"+replace);
            navVos1.add(navVo);
            if (navVos != null){
                navVos.add(navVo);
            }else {
                navVos = navVos1;
            }
        }

        return searchResult;
    }

    /**
     * 用于url地址的替换
     * @param searchParam
     * @param name
     * @param key
     * @return
     */
    private static String getReplace(SearchParam searchParam, String name,String key) {
        String encode = "";
        try {
            encode = URLEncoder.encode(name, "UTF-8");
            encode = encode.replace("+","%20");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
//            将先前的查询条件置为空,此时只是将attr属性替换了，不影响其他品牌等
        String replace = searchParam.get_queryString().replace("&"+key+"=" + encode, "");
        return replace;
    }

    /**
     * 构建请求的方法
     * @return
     */
    private SearchRequest requestBinding(SearchParam searchParam) {

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();   //创建dsl语句都从此builder得到
// 1构建bool - query
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();     //创建query下的bool查询
// 1.1 must模糊匹配
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }

// 1.2 filter过滤内容  term类型, 根据catalog3Id匹配
        if (!StringUtils.isEmpty(searchParam.getCatalog3Id())){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }

// 1.3 根据brandId过滤 terms类型，
        if (!StringUtils.isEmpty(searchParam.getBrandId())){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }

// 1.4 根据hasStock过滤 term类型  按照库存查询为true则查有库存的 若为false则查无库存的
        if (searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }

// 1.5 根据skuPrice过滤 range类型  我们规定传参时一定按照如下格式  100_500  _500  100_
        if (!StringUtils.isEmpty(searchParam.getSkuPrice())){
            String skuPrice = searchParam.getSkuPrice();
            String[] price = skuPrice.split("_");
            if (price.length == 2){
                if (StringUtils.isEmpty(price[0])){
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(price[1]));
                }else {
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(price[0]).lte(price[1]));
                }
            }else if(price.length == 1){
                boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(price[0]));
            }
        }

// 1.6 根据nested的attrs属性过滤   按照 attrs=1_6寸:12寸&attrs=2_7寸:13寸 格式来表示attrs属性
        if (searchParam.getAttrs() != null && searchParam.getAttrs().size() > 0){
            List<String> attrs = searchParam.getAttrs();
            for (String attr: attrs) {

                BoolQueryBuilder nestedboolQueryBuilder = QueryBuilders.boolQuery();   //构建一个BoolQueryBuilder

                String[] split = attr.split("_");
                nestedboolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                String[] split1 = split[1].split(":");
                nestedboolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",split1));
                //每一个attrs都生成一个nested查询

                boolQuery.filter(QueryBuilders.nestedQuery("attrs",nestedboolQueryBuilder, ScoreMode.None));
//                QueryBuilders.nestedQuery("attrs",nestedboolQueryBuilder, ScoreMode.None);     //ScoreMode.None表示不参与评分
            }
        }
//  2.1 将封装好的boolQuery语句包装进searchSourceBuilder里面
        searchSourceBuilder.query(boolQuery);

//  2.2排序  按照 sort=hotScore_asc/desc
        if (!StringUtils.isEmpty(searchParam.getSort())){
            String[] split = searchParam.getSort().split("_");
            String order = split[1];
            SortOrder sortOrder = ("asc".equalsIgnoreCase(order)?SortOrder.ASC:SortOrder.DESC);
            searchSourceBuilder.sort("skuId",sortOrder);
        }

//   2.3 pagenum分页  按照pageNum查询   pageNum:5    默认值为1
        searchSourceBuilder.from((searchParam.getPageNum() - 1) * ProductConstant.PRODUCT_SIZE);
        searchSourceBuilder.size(ProductConstant.PRODUCT_SIZE);

//    2.4 highlight高亮,前提是match的skuTitle里面检索的有数据
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            //执行高亮语句
            searchSourceBuilder.highlighter(highlightBuilder);
        }

//    3.聚合分析
//    3.1 brand_agg品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(10);
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_Img_agg").field("brandImg").size(10));
        searchSourceBuilder.aggregation(brand_agg);
//    3.2 catalog_agg分类名称聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId").size(10);
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catalog_agg);
//     3.3 attr属性聚合分析
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");

//      3.31 attr_id聚合分析
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg");
        attr_id_agg.field("attrs.attrId").size(10);
        attr_agg.subAggregation(attr_id_agg);

//     3.4 attrName聚合分析
        TermsAggregationBuilder aggs_attrName = AggregationBuilders.terms("aggs_attrName");
        aggs_attrName.field("attrs.attrName").size(10);
        attr_id_agg.subAggregation(aggs_attrName);

//     3.5 attrValue聚合分析
        TermsAggregationBuilder aggs_attrValue = AggregationBuilders.terms("aggs_attrValue");
        aggs_attrValue.field("attrs.attrValue").size(10);
        attr_id_agg.subAggregation(aggs_attrValue);

//     3.6 执行nested语句
        searchSourceBuilder.aggregation(attr_agg);

//    4.得到构建的dsl语句
        String s = searchSourceBuilder.toString();
        System.out.println("构建的dsl语句为为/n"+s);

//将构建好的searchRequest返回
        SearchRequest searchRequest = new SearchRequest(new String[]{ProductConstant.PRODUCT_INDEX}, searchSourceBuilder);
        return searchRequest;
    }

}